/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package central.server;

import java.io.EOFException;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import share.AliveCheckThread;
import share.FileCommunicator;
import share.Server;

/**
 *
 * @author mire
 */
public final class CentralServer extends Server implements Runnable {

    //final static private String confName = "user.dir" + File.pathSeparator + ".cs.conf";
    final static private String confName = ".cs.conf";
    private int operatorPort;
    private int aliveTimeout;
    private ConcurrentHashMap<String, CentralOperator> operators;
    private ExecutorService threadPool;
    private boolean restored;

    public CentralServer(int operatorPort, int aliveTimeout) {
        super(0);
        this.operatorPort = operatorPort;
        this.aliveTimeout = aliveTimeout;
        operators = new ConcurrentHashMap<String, CentralOperator>(2);
        backupConfiguration();
        restored = false;
    }

    public CentralServer() {
        super(0);
        operators = new ConcurrentHashMap<String, CentralOperator>(2);
        restoreConfiguration();
        restored = true;
    }

    public int getAliveTimeout() {
        return aliveTimeout;
    }

    public int getOperatorPort() {
        return operatorPort;
    }

    public ExecutorService getThreadPool() {
        return threadPool;
    }

    public void setAliveTimeout(int aliveTimeout) {
        shutdownCheckAliveThreads();
        this.aliveTimeout = aliveTimeout;
        backupConfiguration();
        startCheckAliveThreads();
    }

    public ConcurrentHashMap<String, CentralOperator> getOperators() {
        return operators;
    }

    @Override
    public void run() {

        threadPool = Executors.newCachedThreadPool();
        threadPool.execute(new CentralServerListener(this, operatorPort, threadPool));

        if (restored) {
            updateOperators();
            backupConfiguration();
            startCheckAliveThreads();
        }

        try {
            Thread.sleep(new Date().getTime());         //spavaj 40 godina :P
        } catch (InterruptedException ex) {
            Logger.getLogger(CentralServer.class.getName()).log(Level.SEVERE, null, ex);
            shutdownCheckAliveThreads();
            threadPool.shutdownNow();
            backupConfiguration();
        }

    }

    public synchronized static boolean existsConfugration() {
        File f = new File(confName);
        return f.exists();
    }

    public synchronized void backupConfigurationAsObjects() {
        File f = new File(confName);
        if (f.exists()) {
            f.delete();
        }
        FileCommunicator fc = new FileCommunicator(f);
        try {
            fc.open();
            fc.writeInt(operatorPort);
            fc.writeInt(aliveTimeout);
            if (!operators.isEmpty()) {
                backupOperatorsAsObjects(fc);
            }
            fc.close();
        } catch (IOException ex) {
            Logger.getLogger(CentralServer.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    public synchronized void backupConfiguration() {
        File f = new File(confName);
        if (f.exists()) {
            f.delete();
        }

        FileWriter fw = null;
        PrintWriter outp = null;
        try {
            fw = new FileWriter(f);
            outp = new PrintWriter(fw, true);

            outp.println(operatorPort);
            outp.println(aliveTimeout);
            if (!operators.isEmpty()) {
                backupOperators(outp);
            }

        } catch (IOException ex) {
            Logger.getLogger(CentralServer.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                if (outp != null) {
                    outp.close();
                }
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException ex) {
                Logger.getLogger(CentralServer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public synchronized boolean restoreConfigurationAsObjects() {
        File f = new File(confName);
        if (!f.exists()) {
            return false;
        }
        FileCommunicator fc = new FileCommunicator(f);
        try {
            fc.open();
            operatorPort = fc.readInt();
            aliveTimeout = fc.readInt();
            restoreOperatorsAsObjects(fc);
            fc.close();
        } catch (EOFException ex) {
            //doshli do kraja fajla
        } catch (IOException ex) {
            Logger.getLogger(CentralServer.class.getName()).log(Level.SEVERE, null, ex);
        }
        if (operatorPort == 0 || operators.isEmpty()) {
            return false;
        }

        return true;
    }

    public final synchronized boolean restoreConfiguration() {
        File f = new File(confName);
        if (!f.exists()) {
            return false;
        }

        FileReader fr = null;
        LineNumberReader inr = null;
        try {
            fr = new FileReader(f);
            inr = new LineNumberReader(fr);

            operatorPort = Integer.parseInt(inr.readLine());
            aliveTimeout = Integer.parseInt(inr.readLine());
            restoreOperators(inr);

        } catch (IOException ex) {
            Logger.getLogger(CentralServer.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                if (inr != null) {
                    inr.close();
                }
                if (fr != null) {
                    fr.close();
                }
            } catch (IOException ex) {
                Logger.getLogger(CentralServer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return true;
    }

    private void startCheckAliveThreads() {
        Iterator<CentralOperator> it = operators.values().iterator();
        while (it.hasNext()) {
            CentralOperator o = it.next();
            o.setAliveCheckThread(Executors.newSingleThreadScheduledExecutor());
            o.getAliveCheckThread().scheduleAtFixedRate(
                    new AliveCheckThread(o.getServers(), 0, o.getBackupServersName()),
                    aliveTimeout, aliveTimeout, TimeUnit.SECONDS);
        }
    }

    private void shutdownCheckAliveThreads() {
        Iterator<CentralOperator> it = operators.values().iterator();
        while (it.hasNext()) {
            CentralOperator o = it.next();
            o.getAliveCheckThread().shutdownNow();
        }

    }

    private void backupOperatorsAsObjects(FileCommunicator fc) throws IOException {
        Iterator<CentralOperator> it = operators.values().iterator();
        while (it.hasNext()) {
            CentralOperator o = it.next();
            fc.writeString(o.getName());
            o.backupUsersAsObjects();
            o.backupServersAsObjects();
        }
    }

    private void backupOperators(PrintWriter outp) {
        Iterator<CentralOperator> it = operators.values().iterator();
        while (it.hasNext()) {
            CentralOperator o = it.next();
            outp.println(o.getName());
            o.backupUsers();
            o.backupServers();
        }
    }

    private boolean restoreOperatorsAsObjects(FileCommunicator fc) {
        String operatorName;

        try {
            while (true) {
                operatorName = fc.readString();
                CentralOperator o = new CentralOperator(operatorName);
                o.restoreUsersAsObjects();
                o.restoreServersAsObjects();
                operators.put(operatorName, o);
            }
        } catch (EOFException ex) {
            //doshli do kraja fajla
        } catch (IOException ex) {
            Logger.getLogger(CentralServer.class.getName()).log(Level.SEVERE, null, ex);
        }
        if (operators.isEmpty()) {
            return false;
        }

        return true;
    }

    private boolean restoreOperators(LineNumberReader inr) {
        String operatorName;
        try {
            while ((operatorName = inr.readLine()) != null) {
                CentralOperator o = new CentralOperator(operatorName);
                o.restoreUsers();
                o.restoreServers();
                operators.put(operatorName, o);
            }
        } catch (IOException ex) {
            //kraj fajla
        }
        return true;
    }

    private void updateOperators() {
        Iterator<CentralOperator> it = operators.values().iterator();
        while (it.hasNext()) {
            CentralOperator o = it.next();
            o.update(this);
        }
    }

    public void startListener(String operatorName, Server s) {
        ConcurrentHashMap<Integer, CentralServerListener> serverListeners = operators.get(operatorName).getServerListeners();
        CentralServerListener serverListener = null;
        int key = s.hashCode();
        if (!serverListeners.containsKey(key)) {
            serverListener = new CentralServerListener(this, s.getCentralPort(), s.getAddress() ,this.getThreadPool());
            serverListeners.put(key, serverListener);
            this.getThreadPool().execute(serverListener);
        } else {
            serverListener = serverListeners.get(key);
            if (serverListener.isStoped()) {
                serverListener = new CentralServerListener(this, s.getCentralPort(), this.getThreadPool());
                this.getThreadPool().execute(serverListener);
            }
        }
    }

    public void stopListener(String operatorName, Server s) {
        ConcurrentHashMap<Integer, CentralServerListener> serverListeners = operators.get(operatorName).getServerListeners();
        CentralServerListener serverListener = null;
        int key = s.hashCode();
        if(serverListeners.contains(key)){
            serverListener = serverListeners.get(key);
            serverListener.stop();
        }
    }
}
